﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.ComponentModel;
using ArgusLight.Drawing;

namespace ArgusLight.Fractals2D
{
	/// <summary>
	/// Base class for calculation of 2D fractal images.
	/// </summary>
	/// <ToDo>
	/// </ToDo>
	public abstract class Fractal
	{
		#region Events
		public event ProgressUpdatedHandler ProgressUpdated;
		public event CalculationFinishedHandler CalculationFinished;
		public event CalculationAbortedHandler CalculationAborted;
		#endregion

		#region Fields
		//protected Bitmap image;
		//protected bool isCalculating;
		protected BackgroundWorker backgroundWorker;
		protected FractalParameter parameter;
		protected Coloring coloring;
		private FractalParameter backUpParameter;
		private Coloring backUpColoring;
		#endregion

		#region Properties
		public int Width { get { return this.parameter.PixelWidth; } }
		public int Height { get { return this.parameter.PixelHeight; } }
		//public Bitmap Image { get { return new Bitmap(this.image.WritableBitmap); } }
		public bool IsCalculating { get { return this.backgroundWorker.IsBusy; } }
		public bool IsCancelationPending { get; private set; }
		public double Rotation { get { return this.parameter.Rotation; } }
		public FractalParameter Parameter { get { return this.parameter.GetCopy(); } }
		public Coloring Coloring { get { return this.coloring; } }
		public abstract Type ColoringBase { get; }
		#endregion

		#region Constructor
		public Fractal(int Width, int Height, Coloring coloring)
			:this()
		{
			this.coloring = coloring;
			this.parameter.Rotation = 0;
			this.parameter.PixelWidth = Width;
			this.parameter.PixelHeight = Height;
		}

		public Fractal()
		{
			this.parameter = new FractalParameter();
			this.backgroundWorker = new BackgroundWorker();
			this.backgroundWorker.WorkerReportsProgress = true;
			this.backgroundWorker.WorkerSupportsCancellation = true;
			this.backgroundWorker.DoWork += new DoWorkEventHandler(this.BackgroundWorker_DoWork);
			this.backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(this.BackgroundWorker_ProgressChanged);
			this.backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(this.BackgroundWorker_RunWorkerCompleted);
		}
		#endregion

		#region Methods
		protected double GetRatio()
		{
			return this.Width / (double)this.Height;
		}

		protected virtual void OnProgressUpdated(object sender, ProgressUpdatedArgs e)
		{
			if (this.ProgressUpdated != null)
				this.ProgressUpdated(sender, e);
		}

		protected virtual void OnCalculationFinished(object sender, CalculationFinishedArgs e)
		{
			this.IsCancelationPending = false;
			if (this.CalculationFinished != null)
				this.CalculationFinished(sender, e);
		}

		protected virtual void OnCalculationAborted(object sender, CalculationAbortedArgs e)
		{
			this.IsCancelationPending = false;
			if (this.CalculationAborted != null)
				this.CalculationAborted(sender, e);
		}

		/// <summary>
		/// Override to do the calculating. Don't forget to
		/// - check for cancelationrequests.
		/// - report the progress using this.backgroundWorker.ReportProgress(int).
		/// </summary>
		protected abstract void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e);

		private void BackgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
		{
			ProgressUpdatedArgs args = new ProgressUpdatedArgs();
			args.Progress = e.ProgressPercentage;
			args.UserState = e.UserState;
			this.OnProgressUpdated(this, args);
		}

		private void BackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
		{
			if (e.Cancelled == true)
			{
				this.parameter = this.backUpParameter;
				this.coloring = this.backUpColoring;
				this.OnCalculationAborted(this, new CalculationAbortedArgs());
			}
			else
			{
				this.coloring.PostCalculation();
				this.OnCalculationFinished(this, new CalculationFinishedArgs());
			}
			this.backUpParameter = null;
			this.backUpColoring = null;
		}

		public abstract void SetColoring(Coloring c);

		public bool StartCalculation(FractalParameter parameter)
		{
			if (this.IsCalculating == true)
				return false;
			this.backUpParameter = this.parameter;
			this.parameter = parameter;
			this.backUpColoring = this.coloring;
			this.coloring = this.coloring.CreateNewInstance();
			this.coloring.Parameter = this.backUpColoring.Parameter;
			this.coloring.Reset(parameter);
			this.backgroundWorker.RunWorkerAsync();
			return true;
		}

		public void RequestCalculationCancelation()
		{
			this.IsCancelationPending = true;
			if (this.backgroundWorker.IsBusy == true)
				this.backgroundWorker.CancelAsync();
		}

		public System.Windows.Media.Imaging.BitmapSource GetImage()
		{
			if (this.coloring == null || this.coloring.IsReady == false)
				return null;

			int width = this.coloring.FractalParameter.PixelWidth;
			int heigth = this.coloring.FractalParameter.PixelHeight;
			Bitmap bitmap = new Bitmap(width, heigth);
			for (int x = 0; x < width; x++)
			{
				for (int y = 0; y < heigth; y++)
				{
					Color c = this.coloring.GetColor(x, y);
					bitmap.SetPixel(x, y, c);
					//bitmap.SetPixel(x, y, Colors.Black);
				}
			}
			bitmap.Invalidate();
			return bitmap.WritableBitmap;
		}

		public Fractal CreateNewInstance()
		{
			Type t = this.GetType();
			Fractal fractal = (Fractal)Activator.CreateInstance(t);
			fractal.coloring = this.coloring.CreateNewInstance();
			//fractal.parameter = this.Parameter;
			return fractal;
		}
		#endregion
	}
}
